# 需要先安装
"""
# 用于实现jwt
pip install python-jose[cryptography]
# 用于加密
pip install passlib[bcrypt]


参考文档： https://fastapi.tiangolo.com/zh/tutorial/security/oauth2-jwt/
"""
from datetime import datetime, timedelta
from typing import Optional
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel

# to get a string like this run:
# 生成命令： openssl rand -hex 32
# 创建一个随机的32位16进制的数
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
# 使用算法
ALGORITHM = "HS256"
# token过期时间
ACCESS_TOKEN_EXPIRE_MINUTES = 30

fake_users_db = {
    "lxgzhw001": {
        "username": "lxgzhw001",
        "full_name": "Dapeng Zhang",
        "email": "lxgzhw001@example.com",
        # 对应password： secret
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
        "disabled": False,
    }
}


# token模型
class Token(BaseModel):
    access_token: str
    token_type: str


# token数据
class TokenData(BaseModel):
    username: Optional[str] = None


# 用户模型
class User(BaseModel):
    username: str
    email: Optional[str] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None


# 用户入库模型
class UserInDB(User):
    hashed_password: str


# 密码上下文对象
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# auth对象
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

app = FastAPI()


# 验证密码
def verify_password(plain_password, hashed_password):
    """
    验证密码
    :param plain_password: 明文密码
    :param hashed_password: 加密密码
    :return: 验证结果
    """
    return pwd_context.verify(plain_password, hashed_password)


# 获取hash密码
def get_password_hash(password):
    """
    获取密码
    :param password: 明文密码
    :return: 加密密码
    """
    return pwd_context.hash(password)


# 获取用户
def get_user(db, username: str):
    """
    获取用户
    :param db: 数据库对象
    :param username: 用户名
    :return: 用户名对应的用户
    """
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


# 验证用户
def authenticate_user(fake_db, username: str, password: str):
    """
    验证用户
    :param fake_db: 数据库对象
    :param username: 用户名
    :param password: 密码
    :return:
    """
    # 获取用户
    user = get_user(fake_db, username)
    # 不存在
    if not user:
        return False
    # 密码不对
    if not verify_password(password, user.hashed_password):
        return False
    # 返回获取到的用户
    return user


# 创建token
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    """
    创建token
    :param data: 数据
    :param expires_delta: 过期时间
    :return: token
    """
    # 复制一份数据
    to_encode = data.copy()
    # 如果设置了过期时间
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        # 如果没有设置过期时间
        expire = datetime.utcnow() + timedelta(minutes=15)
    # 增加过期时间
    to_encode.update({"exp": expire})
    # jwt的token
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    # 返回jwt类型的token
    return encoded_jwt


# 获取当前用户
async def get_current_user(token: str = Depends(oauth2_scheme)):
    """
    获取当前用户
    :param token: token
    :return: 当前用户
    """
    # 异常
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        # 如果token不对
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        token_data = TokenData(username=username)
    except JWTError:
        raise credentials_exception
    # 获取用户
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    return user


# 获取激活的用户
async def get_current_active_user(current_user: User = Depends(get_current_user)):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


# 获取token
@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    # 获取auth用户
    user = authenticate_user(fake_users_db, form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    # 过期时间
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    # token
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    # 返回token
    return {"access_token": access_token, "token_type": "bearer"}


@app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user


@app.get("/users/me/items/")
async def read_own_items(current_user: User = Depends(get_current_active_user)):
    return [{"item_id": "Foo", "owner": current_user.username}]
